﻿#region License

// Another free, open solution by someone who gets it.
// 
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

using AutoMapper;

using Patterns.Validation.Advanced;

namespace Patterns.Reflection
{
	///<summary>
	///</summary>
	public class ReflectedObject<T> : IReflection<T> where T : class
	{
		private readonly MemberValueRetrievers<T> _getters;
		private readonly MemberValuePersisters<T> _setters;

		/// <summary>
		/// 	Initializes a new instance of the <see cref = "ReflectedObject{T}" /> class.
		/// </summary>
		/// <param name = "target">The target.</param>
		/// <param name = "mapper">The mapper.</param>
		public ReflectedObject(T target, IMappingEngine mapper)
		{
			_getters = new MemberValueRetrievers<T>(this);
			_setters = new MemberValuePersisters<T>(this, mapper);
			Target = target;
		}

		/// <summary>
		/// 	Gets the type of the target.
		/// </summary>
		/// <value>The type of the target.</value>
		protected Type TargetType
		{
			get { return Target != null ? Target.GetType() : typeof (T); }
		}

		#region IReflection<T> Members

		/// <summary>
		/// 	Gets the value.
		/// </summary>
		/// <param name = "name">The field or property name.</param>
		/// <param name = "valueHandler">The value handler.</param>
		/// <param name = "indexArguments">The index arguments if applicable.</param>
		/// <returns>The current reflection.</returns>
		public IReflection<T> GetValue(MemberName name, Action<object> valueHandler, IEnumerable<object> indexArguments = null)
		{
			Target.Should().NotBeNull();
			name.Should().NotBeNull();
			valueHandler.Should().NotBeNull();

			Action<Action<object>, IEnumerable<object>> retrieval = _getters[name];

			if (retrieval != null) retrieval(valueHandler, indexArguments);

			return this;
		}

		/// <summary>
		/// 	Sets the value.
		/// </summary>
		/// <param name = "name">The field or property name.</param>
		/// <param name = "value">The value.</param>
		/// <param name = "indexArguments">The index arguments if applicable.</param>
		/// <returns>The current reflection.</returns>
		public IReflection<T> SetValue(MemberName name, object value, IEnumerable<object> indexArguments = null)
		{
			Target.Should().NotBeNull();
			name.Should().NotBeNull();

			Action<object, IEnumerable<object>> persistence = _setters[name];

			if (persistence != null) persistence(value, indexArguments);

			return this;
		}

		/// <summary>
		/// 	Runs the method with the specified name.
		/// </summary>
		/// <param name = "name">The method name.</param>
		/// <param name = "arguments">The arguments, if any..</param>
		/// <param name = "returnValueHandler">The return value handler, if any.</param>
		/// <returns>The current reflection.</returns>
		public IReflection<T> Run(MemberName name, IEnumerable<object> arguments = null,
		                          Action<object> returnValueHandler = null)
		{
			Target.Should().NotBeNull();
			name.Should().NotBeNull();

			var method = name.ResolveMember(TargetType) as MethodInfo;
			if (method != null)
			{
				object[] parameters = arguments != null ? arguments.ToArray() : null;
				object result = method.Invoke(Target, parameters);
				if (returnValueHandler != null) returnValueHandler(result);
			}

			return this;
		}

		///<summary>
		///	Gets the reflected object.
		///</summary>
		public T Target{ get; protected set; }

		#endregion

		/// <summary>
		/// 	Gets the method.
		/// </summary>
		/// <param name = "name">The name.</param>
		/// <returns></returns>
		protected virtual MethodInfo GetMethod(MemberName name)
		{
			return name.ResolveMember(TargetType) as MethodInfo;
		}

		/// <summary>
		/// 	Gets the field.
		/// </summary>
		/// <param name = "name">The name.</param>
		/// <returns></returns>
		public virtual FieldOrProperty GetField(MemberName name)
		{
			var fieldInfo = name.ResolveMember(TargetType) as FieldInfo;
			return fieldInfo != null
			       	? new FieldOrProperty
			       	{
			       		MemberValueType = fieldInfo.FieldType,
			       		CanGet = true,
			       		CanSet = !fieldInfo.IsInitOnly,
			       		Get = () => fieldInfo.GetValue(Target),
			       		Set = o => fieldInfo.SetValue(Target, o)
			       	}
			       	: null;
		}

		/// <summary>
		/// 	Gets the indexed property.
		/// </summary>
		/// <param name = "name">The name.</param>
		/// <returns></returns>
		public virtual IndexedProperty GetIndexedProperty(MemberName name)
		{
			var propertyInfo = name.ResolveMember(TargetType) as PropertyInfo;
			if (propertyInfo == null) return null;

			ParameterInfo[] indexParameters = propertyInfo.GetIndexParameters();

			return indexParameters.Length > 0
			       	? new IndexedProperty
			       	{
			       		MemberValueType = propertyInfo.PropertyType,
			       		CanGet = propertyInfo.CanRead,
			       		CanSet = propertyInfo.CanWrite,
			       		Get = i => propertyInfo.GetValue(Target, i),
			       		Set = (i, v) => propertyInfo.SetValue(Target, v, i)
			       	}
			       	: null;
		}

		/// <summary>
		/// 	Gets the property.
		/// </summary>
		/// <param name = "name">The name.</param>
		/// <returns></returns>
		public virtual FieldOrProperty GetProperty(MemberName name)
		{
			var propertyInfo = name.ResolveMember(TargetType) as PropertyInfo;
			return propertyInfo != null
			       	? new FieldOrProperty
			       	{
			       		MemberValueType = propertyInfo.PropertyType,
			       		CanGet = propertyInfo.CanRead,
			       		CanSet = propertyInfo.CanWrite,
			       		Get = () => propertyInfo.GetValue(Target, null),
			       		Set = o => propertyInfo.SetValue(Target, o, null)
			       	}
			       	: null;
		}
	}
}